package com.cloud.point.concurrent.general;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 同步模式之顺序控制
 *
 * @author 24373
 * @version 1.0
 * @date 2024-06-16 13:03
 */
@Slf4j
public class TestSyncModel {

    public static void main(String[] args) {
        //waitNotifyBA();
        //parkBA();
        //waitNotifyABC();

        //下列是抽象实现
        //waitNotifyPrintABC();
        reentrantLockABC();
        //parkABC();

    }

    /**
     * park顺序控制
     * 顺序打印ABC5次
     */
    static Thread t1;
    static Thread t2;
    static Thread t3;

    private static void parkABC() {
        ParkPrint lock = new ParkPrint(10000);
        t1 = new Thread(() -> lock.print("A", t2));
        t2 = new Thread(() -> lock.print("B", t3));
        t3 = new Thread(() -> lock.print("C", t1));
        t1.start();
        t2.start();
        t3.start();

        LockSupport.unpark(t1);

        // 封装后
        ParkPrint2 p2 = new ParkPrint2(10);
        p2.setThreads(new Thread[]{
                new Thread(() -> p2.print("A")),
                new Thread(() -> p2.print("B")),
                new Thread(() -> p2.print("C"))
        });
        p2.start();

    }

    /**
     * ReentrantLock顺序控制
     * 顺序打印ABC5次
     */
    private static void reentrantLockABC() {
        ReentrantPrint lock = new ReentrantPrint(100000);
        Condition c1 = lock.newCondition();
        Condition c2 = lock.newCondition();
        Condition c3 = lock.newCondition();
        new Thread(() -> lock.reentrantPrintABC(c1, c2, "A"), "t1").start();
        new Thread(() -> lock.reentrantPrintABC(c2, c3, "B"), "t2").start();
        new Thread(() -> lock.reentrantPrintABC(c3, c1, "C"), "t3").start();

        lock.start(c1);

    }

    /**
     * wait-notify顺序控制
     * -2 抽象方法
     * 顺序打印ABC5次
     */
    private static void waitNotifyPrintABC() {
        WaitPrint waitPrint = new WaitPrint(1, 500000);
        new Thread(() -> waitPrint.waitNotifyPrintABC(1, 2, "A")).start();
        new Thread(() -> waitPrint.waitNotifyPrintABC(2, 3, "B")).start();
        new Thread(() -> waitPrint.waitNotifyPrintABC(3, 1, "C")).start();
    }

    /**
     * wait-notify顺序控制
     * 顺序打印ABC5次
     */
    static final Object lock2 = new Object();
    static boolean flag2_A = true;
    static boolean flag2_B = false;
    static boolean flag2_C = false;

    private static void waitNotifyABC() {
        new Thread(() -> {
            synchronized (lock2) {
                for (int i = 0; i < 5; i++) {
                    while (!flag2_A) {
                        try {
                            lock2.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.info("A");
                    flag2_A = false;
                    flag2_B = true;
                    lock2.notifyAll();
                }
            }
        }).start();
        new Thread(() -> {
            synchronized (lock2) {
                for (int i = 0; i < 5; i++) {
                    while (!flag2_B) {
                        try {
                            lock2.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.info("B");
                    flag2_B = false;
                    flag2_C = true;
                    lock2.notifyAll();
                }
            }
        }).start();
        new Thread(() -> {
            synchronized (lock2) {
                for (int i = 0; i < 5; i++) {
                    while (!flag2_C) {
                        try {
                            lock2.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.info("C");
                    flag2_C = false;
                    flag2_A = true;
                    lock2.notifyAll();
                }
            }
        }).start();
    }

    /**
     * park-unpark顺序控制
     * 先打印B，再打印A
     */
    private static void parkBA() {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.info("A");
        });
        t1.start();

        new Thread(() -> {
            log.info("B");
            LockSupport.unpark(t1);
        }).start();
    }


    /**
     * wait-notify顺序控制
     * 先打印B，再打印A
     */
    static final Object lock1 = new Object();
    static boolean flag1 = false;

    private static void waitNotifyBA() {
        new Thread(() -> {
            synchronized (lock1) {
                while (!flag1) {
                    try {
                        lock1.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.info("A");
            }
        }).start();

        new Thread(() -> {
            synchronized (lock1) {
                log.info("B");
                flag1 = true;
                lock1.notifyAll();
            }
        }).start();

    }

}

/**
 * 抽象 park/unpark 线程交替通信
 */
@Slf4j
class ParkPrint2 {
    final private int times;
    private Thread[] threads;

    public ParkPrint2(int times) {
        this.times = times;
    }

    public void setThreads(Thread[] threads) {
        this.threads = threads;
    }

    public void print(String str) {
        for (int i = 0; i < times; i++) {
            LockSupport.park();
            log.info(str);
            LockSupport.unpark(nextThread());
        }
    }

    private Thread nextThread() {
        Thread t = Thread.currentThread();
        int index = 0;
        for (int i = 0; i < threads.length; i++) {
            // 比较对象的内存地址
            if (threads[i] == t) {
                index = i;
                break;
            }
        }
        if (index < threads.length - 1) {
            return threads[index + 1];
        } else {
            return threads[0];
        }
    }

    public void start() {
        for (Thread t : threads) {
            t.start();
        }
        // 释放第一个线程
        LockSupport.unpark(threads[0]);
    }

}

@Slf4j
@AllArgsConstructor
class ParkPrint {
    private int times;

    // 当前线程调用；谁调用谁阻塞
    public void print(String str, Thread next) {
        for (int i = 0; i < times; i++) {
            LockSupport.park();
            log.info(str);
            LockSupport.unpark(next);
        }
    }
}

/**
 * 抽象 reentrantLock 线程交替通信
 */
@Slf4j
@AllArgsConstructor
class ReentrantPrint extends ReentrantLock {

    private int times;

    public void start(Condition c) {
        this.lock();
        try {
            log.info("开始打印");
            c.signal();
        } finally {
            this.unlock();
        }
    }

    public void reentrantPrintABC(Condition cur, Condition next, String str) {
        this.lock();
        try {
            for (int i = 0; i < times; i++) {
                /**
                 * 所有变量先阻塞等待；再进行排序输出
                 */
                //log.info("{}=1 锁", Thread.currentThread().getName());
                // 释放锁并进入等待
                cur.await();
                log.info(str);
                /**
                 * 唤醒其它线程之前，需要保证当前线程提前进入等待队列（用await去释放锁）
                 * 防止被唤醒线程提前进入流程唤醒其它线程（无效唤醒，因为需要被唤醒的线程还没有进入等待队列）;
                 * 无效唤醒后，所有线程依次就会进入waiting状态等待唤醒
                 */
                next.signal();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //log.info("{}=2 解", Thread.currentThread().getName());
            this.unlock();
        }
    }
}

@Slf4j
@AllArgsConstructor
class WaitPrint {
    /**
     * 使用 下标作为等待标记
     */
    private int index;
    private int times;

    public void waitNotifyPrintABC(int curIndex, int nextIndex, String str) {
        synchronized (this) {
            for (int i = 0; i < times; i++) {
                while (index != curIndex) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.info(str);
                index = nextIndex;
                this.notifyAll();
            }
        }
    }
}